#!/usr/bin/env python
#-*-coding:utf-8-*-

'''
    TODO:
    умлауты в хэшах
    скобки в хешах    
'''
import MySQLdb

import xml.dom.minidom

from cgi import escape
import re
import sys, os, cgi, time
from flup.server.fcgi import WSGIServer

import urllib

import inc

import locale

#inc.log(context, 'new instance begin...')
n = 0

fpid = open("logs/lepserver.pid", "r+")

prid = fpid.read()
prid = prid[0:3]

#inc.log(context, 'old value str: [%s]' % prid)

pridi = int(prid)

#inc.log(context, 'last used id was: %i' % pridi)
pridi += 1

prid = str(pridi).zfill(3)
#inc.log(context, 'new id is: %s' % prid)

fpid.seek(0)
fpid.write(prid)
fpid.close()

inc.setid(prid)

inc.log({'addr': 'not yet'}, 'new instance: [%s]' % prid, 2)

#db = None
#cursor = None

def app(env, start_response):
    context = {}
    global n
    n = n + 1
    
    addr = env.get("REMOTE_ADDR")
    
    context['n'] = n
    context['httpagent'] = env.get("HTTP_USER_AGENT")
    context['agent'] = ''
    context['aver'] = ''
    context['addr'] = addr
    context['db'] = ''
    
    inc.log(context, '')
    inc.log(context, 'new connection (%i)' % n)
    
    start_response('200 OK', [('Content-Type', 'text/xml')])
    
    db = inc.dbl(addr) #oh my...
    context['db'] = db
    
    xmlbody = ''
    try:
        length = int(env.get('CONTENT_LENGTH', '0'))
    except ValueError:
        length = 0
        
    if length == 0:
        xmlbody = urllib.unquote(env.get('QUERY_STRING'))
        xmlbody = xmlbody.replace('\\"', '"')
        
        #foo_lui_lyrics3
        if xmlbody.find('<query agent="foo_uie_lyrics3"') != -1:
           xmlbody = '<?xml version="1.0"?>' + xmlbody
           
        logfile = open("logs/foo_uie_lyrics3.log", "a")
        logfile.write("from: %s\n" % addr )
        logfile.write(xmlbody)
        logfile.write("\n\n")
    
    if length != 0:
        try:
            xmlbody  = env['wsgi.input'].read(length) # read exception sometimes
        except IOError:
            inc.log(context, context, 'read() exception') #should give some responce?
            return
        
        logfile = open("logs/foo_post_data.log", "a")
        logfile.write("from: %s\n" % addr )
        logfile.write(xmlbody)
        logfile.write("\n\n")
        
        #musicbee amp replace
        if xmlbody.find('<query agent="musicbee"') != -1:
           xmlbody = xmlbody.replace('&', '&amp;') 
           xmlbody = xmlbody.replace('\'', '&apos;') 
        
    xmlstr =  '<?xml version="1.0"?>\n'
    xmlstr += '<response>\n'
    
    songs = parse(context, xmlbody)
    
    db.close()
    
    if songs:
        xmlstr += songs

    xmlstr += '</response>'
    
    #inc.log(context, context, xmlstr)
    
    return str(xmlstr.encode('utf-8'))

def make_hash (st, artist = None):
    #set artist to true to get artist hashes, they're a little different

    st = st.decode("utf-8")
    st = st.lower()
    
    st = st.replace('&', 'and')

    if artist:
        # get rid of standing alone 'the' articles
        # why not 'a'?
        re_the = re.compile('(^|\s)the(\s|$)')
        st = re_the.sub('', st)
        
    p = re.compile('\W', re.UNICODE)
    p2 = re.compile('\s', re.UNICODE)
    
    br = re.compile('\(.+\)', re.UNICODE)

    st = br.sub('', st)
    
    s = p.sub('', st)
    
    if s == '':
        s = p2.sub('', st)
        
    s = s.encode("utf-8")
    return s

def save_stats(context, artist, title, album, action, got): # checkpoint ??
    db = context['db']
    
    addr = context['addr']
    aver = context['aver']
    agent = context['agent']
    
    if not artist or not title:
        return 
        
    ahash = make_hash(artist, True)
    thash = make_hash(title)
    
    if action == 'req':
        db.sql ('''SELECT time
            FROM cst
            WHERE time > DATE_SUB(NOW(),INTERVAL 16 MINUTE)
                AND author = %s
                AND title = %s
                AND addr= %s''', (artist, title, addr))
        
        row = db.cursor.fetchone()
        
        if row:
            inc.log(context, 'has been searched a second ago... (%s - %s) [%s]' % (artist, title, addr), 1)
            return
            
    db.sql('''INSERT INTO cst
            (addr, author, ahash, title, thash, album, action, got, agent, aver)
            VALUES
            (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s)''',
            (addr, artist, ahash, title, thash, album, action, got, agent, aver))
            
    return True

def get_lyrics(context, artist, title, album):
    db = context['db']
    
    artist = artist.encode('utf-8')
    title = title.encode('utf-8')

    if not artist or not title:
        inc.log(context, 'if not artist or not title...', 1)
        return
    inc.log(context, 'requested: %s ~ %s' % (artist, title))
    
    ahash = make_hash(artist, True)
    thash = make_hash(title)
    inc.log(context, 'hashes: %s ~ %s' % (ahash, thash))
    
    db.sql("SELECT aid FROM author where ahash = %s", (ahash))
    row = db.cursor.fetchone()
    
    if not row:
        db.sql("SELECT aid FROM authoralias where alhash = %s", (ahash))
        row = db.cursor.fetchone()

    if not row:
        inc.log(context, 'artist not found (%s)' % (artist))
        save_stats (context, artist, title, album, 'req', None)
        return
        
    aid = int(row[0])
    
    db.sql('''SELECT LyID, lyrics
                FROM lyrics
                WHERE AID=%s
                    AND thash=%s
                    AND freeze IS NULL
                LIMIT 1''', (aid, thash))
                
    row = db.cursor.fetchone()
    
    if not row:
        inc.log(context, 'lyrics not found (%s ~ %s)' % (artist, title))
        save_stats (context, artist, title, album, 'req', None)
        return
        
    lyid = row[0]
    inc.log(context, 'found, lyid = %s, fcgi counter = %i' % (lyid, n))
    
    try:
        lyrics = row[1]
    except:
        inc.log(context, '%s - %s, exception on row[1]' % (artist, title), 2)
        return
    
    res = save_stats (context, artist, title, album, 'req', 1)
    
    if res:
        db.sql('update lyrics set cnt = cnt + 1, time = time where lyid = %s', lyid)
        inc.log(context, 'updating counter')
    else:
        inc.log(context, 'just been updated...')
        
    return lyrics

def process_query(context, query):
#    songs = query.getElementsByTagName('song')
#    that'll find all the songs nested in <update> too, 'couse it's damn recursive

    songs = query.childNodes

    xmlstr = ''
    ctotal = 0
    cgot = 0
    
    for song in songs:
        if (song.tagName == 'song'):
            ctotal += 1
            id = song.getAttribute('id')
            title = song.getAttribute('title')
            artist = song.getAttribute('artist')
            album = song.getAttribute('album')
            
            lyrics = get_lyrics(context, artist, title, album)
            # save_stats(artist, title)

            if lyrics:
                cgot += 1
                xmlstr += u'<song id="'+ id +'"><text source="$null">'
                #xmlstr += cgi.escape(lyrics.decode('utf-8')).encode('utf-8')
                l2 = cgi.escape(lyrics)
                #inc.log(context, l2)
                xmlstr += l2
                xmlstr += u'</text></song>'
        # else: -- it's update "query" (bad bad xml format…)
            

    db = context['db']
    #requests_total_song
    if ctotal > 0:
        db.sql('update variables set value = value + %s where VID = 2', ctotal);

    #requests_total_song_got:
    if cgot > 0:
        db.sql('update variables set value = value + %s where VID = 3', cgot);

    return xmlstr

def get_artist(context, artist):
    '''
        get or create, actualy
    '''
    
    inc.log(context, 'get artist: %s' % artist )
    
    db = context['db']
    ahash = make_hash(artist, True)
    
    db.sql("SELECT aid FROM author where ahash = %s", (ahash))
    row = db.cursor.fetchone()
    
    if not row:
        cursor = db.sql("SELECT aid FROM authoralias where alhash = %s", (ahash))
        row = db.cursor.fetchone()

    if not row:
        inc.log(context, 'new artist')
        
        db.sql('''INSERT INTO author (author, ahash)
                VALUES (%s, %s)''', (artist, ahash))
                
        aid = db.cursor.lastrowid
        
        return aid
        
    aid = int(row[0])
    
    return aid

def check_user(context, login, pwd):

    db = context['db']
    db.sql('select UID from users where login = %s and pwd = %s', (login, pwd));
    
    row = db.cursor.fetchone()
    
    if row:
        return int(row[0])
    else:
        return None
    
def process_update(context, update, uid):

    songs = update.getElementsByTagName('song')
    
    db = context['db']
    stats_total = 0
    stats_was = 0

    inc.log(context, 'update...')
    
    for song in songs:
        id = song.getAttribute('id')
        title = song.getAttribute('title')
        album = song.getAttribute('album')
        artist = song.getAttribute('artist')

        artist = artist.encode('utf-8')
        title = title.encode('utf-8')
        
        # but the lyrics is nested in <song ...><text>(here)</text></song>, so
        
        inc.log(context, 'song...')

        text = song.firstChild.firstChild.data # don't ask me, ask DOM

        aid = get_artist(context, artist)
        
        inc.log(context, 'artist found/created')
        thash = make_hash(title)
        
        ra = 0
        
        if uid: 
            db.sql('''INSERT into lyrics
                        (uid, aid, title, thash, album, lyrics)
                        VALUES (%s, %s, %s, %s, %s, %s)''', (uid, aid, title, thash, album, text))
                        
            lyid = db.cursor.lastrowid
                        
            # freeze everything older (need to implement carma stuff here)
            db.sql('''UPDATE lyrics
                        SET freeze = TRUE,
                        time = time
                        WHERE aid = %s AND thash = %s
                        AND lyid != %s''', (aid, thash, lyid))
                        
            ra = db.cursor.rowcount
        else:
            db.sql('''INSERT into lyrics
                        (aid, title, thash, album, lyrics)
                        VALUES (%s, %s, %s, %s, %s)''', (aid, title, thash, album, text))
                        
            lyid = db.cursor.lastrowid
            
            db.sql('''UPDATE lyrics
                        SET freeze = TRUE,
                        time = time
                        WHERE 
                        aid = %s AND thash = %s
                        AND uid = NULL
                        AND lyid != %s''', (aid, thash, lyid))
                        
            ra = db.cursor.rowcount

        save_stats (context, artist, title, album, 'post', None)
        
        if ra:
            stats_was += 1
            
        stats_total += 1
    
    return (stats_total, stats_was)
    
def process_register(context, register):
    login = register.getAttribute('login')
    pwd = register.getAttribute('password')
    email = register.getAttribute('mail')

    login = login.encode('utf-8')
    pwd = pwd.encode('utf-8')
    email = email.encode('utf-8')
    
    if not login or not pwd:
        return '<register login="%s" status="fail" />' % (login);
        
    pwd = pwd[0:15]
    
    login = login.lower()
    email = email.lower()
    
    inc.log(context, 'register: %s %s %s' %(login, pwd, email), 3)
    
    '''
        should check login and email here
    '''
    
    db = context['db']
    db.sql('select uid from users where login = %s', login);
    row = db.cursor.fetchone()
    
    if not row:
        #new
        db.sql('INSERT INTO users (login,pwd,email) VALUES (%s, %s, %s) ', (login, pwd, email))
        status = 'ok'
        
    else:
        db.sql('select uid, email from users where login = %s and pwd = %s', (login, pwd));
        row = db.cursor.fetchone()
    
        if row:
            if email == row[1] or (not email):
                inc.log(context, 'email, dbmail = %s %s' % (email, row[1]))
                status = 'ko'
            else:
                uid = int(row[0])
                db.sql('update users set email = %s where uid = %s', (email, uid));
                status = 'kom'
        else:
            status = 'fail'
    
    xmlstr = '<register login="%s" status="%s" />' % (login, status);
    
    inc.log(context, 'status = ' + status)
    
    return xmlstr
    
def parse(context, xmlstr):
    global aver
    global agent 
    
    t1 = time.time()

    #dom = xml.dom.minidom.parseString(xmlstr)
    
    try:
        dom = xml.dom.minidom.parseString(xmlstr)
    except:
        inc.log(context, 'parsing shit happened')
        inc.log(context, 'xml = [%s]\n' % xmlstr)
        return

    queries = dom.getElementsByTagName('query')
    t2 = time.time()

    inc.log(context, 'dom parsing time: %s,     n = %i' % (str(t2-t1), n))
    
    xmlstr = ''

    db = context['db']
    db.sql ('update variables set value = value + 1 where VID = 1')
    
    for query in queries:
        context['agent'] = query.getAttribute('agent')
        context['aver'] = query.getAttribute('version')
        
        if not query.getElementsByTagName('update'):
            xmlstr += process_query(context, query)

    updates = dom.getElementsByTagName('update')

    if (updates):
        stats_was = 0
        stats_total = 0

        uid = None
        
        for update in updates:
            login = update.getAttribute('login')
            pwd = update.getAttribute('password')
            
            if login:
                uid = check_user(context, login, pwd)
                
                if uid >= 0: 
                    inc.log(context, 'l/p: %s, %s - uid = %i (Ok)' % (login, pwd, uid))
                else:
                    inc.log(context, '[W] l/p: %s, %s' % (login, pwd), 1)
                    
                    xmlstr = '<register status="fail" />'
                    
                    return xmlstr
            
            inc.log(context, 'fore process_update')
            (s1, s2) = process_update(context, update, uid)

        stats_total += s1
        stats_was += s2

        stats_xml = '<update><statistics added="'+str(stats_total)+'" was="'+ str(stats_was)+'" error="0" /></update>'
        
        xmlstr += stats_xml
        
    registers = dom.getElementsByTagName('register')
    
    if (registers):
        for register in registers:
            xmlstr += process_register(context, register)
    
    #inc.log(context, xmlstr)
    
    t3 = time.time()
    
    inc.log(context, 'procesing: %s, total: %s' % (str(t3-t2), str(t3-t1)));
    return xmlstr
    
WSGIServer(app).run()

